﻿using System;
using System.Linq;
using System.Reflection;
using NLite.Mini.Internal;
using NLite.Reflection;
using NLite.Internal;
using NLite.Collections;

namespace NLite.Mini.Listener
{
    /// <summary>
    /// 
    /// </summary>
    public class InjectionManyListener:ComponentListenerAdapter
    {
        /// <summary>
        /// 
        /// </summary>
        public InjectionManyListener() : base(ComponentListenStage.PostCreation) { }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ctx"></param>
        public override void OnPostCreation(NLite.Mini.Context.IPostCreationContext ctx)
        {
            if (ctx.Component.Implementation == null
               || ctx.Instance == null)
                return;

            const string Key = "InjectionMany";
            Lazy<InjectionManyMemberModel[]> lazy = null;
            if (ctx.Component.ExtendedProperties.Contains(Key))
                lazy = ctx.Component.ExtendedProperties[Key] as Lazy<InjectionManyMemberModel[]>;

            if (lazy == null)
            {
                const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

                lazy = new Lazy<InjectionManyMemberModel[]>(() =>
                {
                    var instanceType = ctx.Component.Implementation;
                    return (from item in instanceType.GetFields(Flags)
                            let attr = item.GetAttribute<InjectManyAttribute>(true)
                            where attr != null
                            select new InjectionManyMemberModel { Reinjection = attr.Reinjection, MemberType = item.FieldType, Member = item })
                                .Union(
                                from item in instanceType.GetProperties(Flags)
                                let attr = item.GetAttribute<InjectManyAttribute>(true)
                                let ps = item.GetIndexParameters()
                                where attr != null && (ps == null || ps.Length == 0)
                                select new InjectionManyMemberModel { Reinjection = attr.Reinjection, MemberType = item.PropertyType, Member = item })
                                 .Union(
                                from item in instanceType.GetMethods(Flags)
                                let attr = item.GetAttribute<InjectAttribute>(true)
                                let ps = item.GetParameters()
                                where attr != null && item.ReturnType == Types.Void
                                where ps.Any(p => p.HasAttribute<InjectManyAttribute>(false))
                                select new InjectionManyMemberModel { MemberType = item.ReturnType, Member = item })
                                .ToArray();
                });

                ctx.Component.ExtendedProperties[Key] = lazy;
            }

            if (lazy.Value.Length == 0)
                return;

            foreach (var item in lazy.Value)
            {
                if (item.Member.MemberType == MemberTypes.Method)
                    (item.Member as MethodInfo).FastProcInvoke(ctx.Instance, ReflectionHelper.GetParameters(Kernel, (item.Member as MethodInfo).GetParameters()));
                else if (MemberMatcher.MatchMany(item.MemberType, ctx.Kernel))
                    item.Member.Set(ctx.Instance, InjectService.Get(null, item.MemberType, Kernel, true));
            }
        }
    }
}
